昨天的內容介紹了關於各個原始型別基本的特性。
今天主要就幾個主題來討論:
typeof 是一個原生的運算符,給予一個變數,返回一個字串表示該變數的型別。
console.log(typeof 1);//number
console.log(typeof 1n);//bigint
console.log(typeof 'abc');//string
console.log(typeof undefined);//undefined
console.log(typeof true);//boolean
console.log(typeof Symbol('foo'));//symbol
//複合型別
console.log(typeof {});//object
console.log(typeof function foo());//function
以上是所有目前版本 typeof 可能的回傳值。
這個函式其實有不少需要注意的例外,比如原始型別中的 null
console.log(typeof null);//object
typoeof null 的情況會回傳為 object,這個是一個歷史悠久的錯誤,但因為現在可能有許多網路上的實作基於這個前提,目前改也是改不動,就這樣一直將錯就錯了。
上面展示複合型別的結果的時候,可以留意到,嚴格來說 function 其實也算一種 object 的子型別,但 typeof 會特別顯示為 function 而非 object,讓我們在使用上更容易去判斷函式。
其餘的子型別如 array,則一律回傳為 object,需要判斷型別的話,得用如 Array.isArray()
等特別的函式來做判斷。
而且 typeof 是一個好用於判斷 undefined 的方式,在 JS 中直接使用 undefiend 往往會遇到例外,但使用 typeof 能夠安全的判斷是否為 undefined 再繼續做接下來的行為。
一個等於是賦值,兩個等於和三個等於都是用於比較。
兩個等於稱作相等運算符,三個等於稱作嚴格相等運算符。
來看一個最簡單的例子:
let a = '1';
let b = 1;
console.log(a == b);//true
console.log(a === b);//false
等於做的事情是 JS 會先判斷兩方的型別是否相等,如不相等,則嘗試轉型為兩方一致後再進行比較。
相等運算符的隱式轉換依據以下原則進行:
console.log(true == 1);//true
console.log(false == 0);//true
console.log(true == "a");//false
console.log(undefined == null);//true
console.log(null == false);//false
console.log(undefined == '1');//false
let arr = [1,2];
console.log(arr == '1,2');//true
console.log(arr.toString());//"1,2"
console.log(Number('a'));//NaN
console.log(1/0);//Infinity
console.log(0/0);//NaN
console.log(typeof (0/0));//number
同時,NaN 是 JavaScript 中唯一一個自身與自身並不相等的變數,這意味著如果要判斷是否 NaN 時無法使用相等運算符來判斷,必須使用 Number.isNaN 的函式來判斷。
console.log(Infinity == Infinity);//true
console.log(NaN == NaN);//false
console.log(Number.isNaN(Number({})));//true
嚴格相等運算符 === 則是不會進行 隱式轉換,直接進行比較。
let a = '1';
let b = 1;
console.log(a == b);//true
console.log(a === b);//false
容我複製上面的例子,a 和 b 進行嚴格相等運算符比較時,會拿到 false 的結果,因為字串和數字並不相等。
如果你對隱式轉換會發生的事情不夠確定,也不確定自己拿到的內容,最佳實踐上會是盡量在能夠使用嚴格運算符 === 的時候就使用 === 嚴格運算符。
在 YDKJS 中,作者其實對 == 的隱式轉換並不是持有強烈的反對態度,而是表示我們應該要去理解隱式轉換中發生的事情,並在明確知道意圖的情況下可以使用隱式轉換來獲得一些好處。
那讓我們接著看下一個主題:隱式轉換和顯示轉換。
前面其實已經數次提到這兩個詞。
型別轉換是程式中的常見場景,用於將一個型別的變數轉換為另一個型別。在 JavaScript 這種動態類型語言中,轉換常被稱為強制型別轉換
(coercion)。
依據轉換的方式,我們可以再分為顯示轉換和隱式轉換。
顯示轉換表示在程式碼中我們明確使用了某個函數或方法來進行型別的轉換,例如:
let a = 1;
console.log(a);//1
console.log(String(a));//"1"
這個例子中的 String() 明確表示了轉換的意圖,即為顯示轉換。
let a = 1;
let b = '1';
console.log(a == b);//true
複習上一個段落提到的,相等運算符會進行隱式轉換,上面的程式碼中我們沒有直接看到轉換的意圖,但在相等運算符的機制下,他進行了隱式轉換把字串轉為數字進行比較。
隱式轉換常見於各種場景,隱式轉換的結果必定被轉換為一個原始型別,大多是轉換為 字串,數字,或是布林值。除了相等運算符,我們可以再看一下幾個例子:
let a = 1;
console.log(typeof ("" + a));//string
console.log(typeof (a + ""));//string
console.log(typeof ({} + ""));//string
console.log(2-"1");//1
console.log(2-"");//2,空字串會被轉換為 0
console.log(2-"a");//NaN
console.log(Boolean(false));
console.log(Boolean(0), Boolean(-0));
console.log(Boolean(''),Boolean(""),Boolean(``));
console.log(Boolean(null));
console.log(Boolean(undefined));
console.log(Boolean(NaN));
//全部都是 false
為什麼要提到 falsy 列表?因為 boolean 在 JS 中只有兩種值,true 和 false,所有不在 falsy 列表中的值,進行轉換成 boolean 的行為時,永遠會是 true,又稱為 truthy。
只要記住這些 falsy 的值,剩下就都是 truthy 了,也許是做個筆記或是背一下,這個列表還是很有用的。
//即使這種 case 「感覺」上可能是 false,但正如上面說的,只要不在 falsy 表裡,就會是 true
console.log(Boolean({}));//true
console.log(Boolean([]));//true
那什麼情況會隱式轉換一個值為 boolean 值?
我們可以使用 ! 反邏輯運算符來進行轉換,而因為這樣會導致值的相反,開發者會使用 !! 來得到一個與該值相對應的布林值。
console.log(typeof 2);//number
console.log(!2,typeof !2);//false , boolean
console.log(!!2,typeof !!2);//true , boolean
常使用 JS 的人應該對 Parser 並不陌生,我指的 Parser 是如同:
let weight = '100g';
let weightValue = Number.parseInt(weight);
console.log(weight, weightValue);//"100g", 100
console.log(Number(weight));//NaN
像 Number.parseInt
這樣的存在,Parser 往往具有一些定義好的規則,以 Number.parseInt
為例,他的剖析規則是從左往右確認輸入,直到找到第一個無法轉為 Number 的數字停下來(g),剖析該字元之前的數字。
不同的是,這個情況使用 Number() 的方式來進行強制型別轉換則是出現 NaN
,因為強制型別轉換並沒有像剖析器這樣定義特別規則。
特別提一下關於這點來表明剖析器和本文探討的強制型別轉換是兩種不同的機制。
在強制型別轉換中,會時常看到我們使用 String(), Number() 等方式來進行強制型別轉換。
注意!在沒有意識的情況,不要寫成 new String()。
兩者的差異讓我們來看程式碼:
let a = 456;
let aStr = String(a);
let aStr2 = new String(a);
console.log(aStr, typeof aStr);//"456", "string"
console.log(aStr2, typeof aStr2);//"456", "object"
雖然印出來都是 "456",但是因為 console.log
的情況隱含了 .toString()
的隱式型別轉換。
用 typeof
可以看出來,加入了 new
的關鍵字後,變成了透過建構的方式創建了一個之前提到的 String 包裝物件,型別為物件 Object
而不再是 String。
一般來說,包裝物件的初始化多留給 JS 引擎去在背後處理,若一般開發中需要用到轉型,應該先轉型後再去使用該型別的對應方法。